/*
 *   FILE: s5fs_subr.c
 * AUTHOR: afenn
 *  DESCR:
 *  $Id: s5fs_subr.c,v 1.1.2.1 2006/06/04 01:02:15 afenn Exp $
 */

#include "kernel.h"
#include "util/debug.h"
#include "mm/kmalloc.h"
#include "globals.h"
#include "proc/sched.h"
#include "proc/kmutex.h"
#include "errno.h"
#include "util/string.h"
#include "util/printf.h"
#include "mm/pframe.h"
#include "mm/mmobj.h"
#include "drivers/dev.h"
#include "drivers/blockdev.h"
#include "fs/stat.h"
#include "fs/vfs.h"
#include "fs/vnode.h"
#include "fs/s5fs/s5fs_subr.h"
#include "fs/s5fs/s5fs.h"
#include "mm/mm.h"
#include "mm/page.h"

#define dprintf(...) dbg(DBG_S5FS, __VA_ARGS__)

#define s5_dirty_super(fs)                                           \
        do {                                                         \
                pframe_t *p;                                         \
                int err;                                             \
                pframe_get(S5FS_TO_VMOBJ(fs), S5_SUPER_BLOCK, &p);   \
                KASSERT(p);                                          \
                err = pframe_dirty(p);                               \
                KASSERT(!err                                         \
                        && "shouldn\'t fail for a page belonging "   \
                        "to a block device");                        \
        } while (0)


static void s5_free_block(s5fs_t *fs, int block);
static int s5_alloc_block(s5fs_t *);

/*
 * Return the disk-block number for the given seek pointer (aka file
 * position).
 *
 * If the seek pointer refers to a sparse block, and alloc is false,
 * then return 0. If the seek pointer refers to a sparse block, and
 * alloc is true, then allocate a new disk block (and make the inode
 * point to it) and return it.
 *
 * Be sure to handle indirect blocks!
 *
 * If there is an error, return -errno.
 *
 * You probably want to use pframe_get, pframe_pin, pframe_unpin, pframe_dirty.
 */
int
s5_seek_to_block(vnode_t *vnode, off_t seekptr, int alloc)
{
    /* such a WAT */

    s5_inode_t* inode = VNODE_TO_S5INODE(vnode);
    s5fs_t* s5 = VNODE_TO_S5FS(vnode);
    mmobj_t* mm = S5FS_TO_VMOBJ(s5);
    uint32_t block = S5_DATA_BLOCK(seekptr);
    int num = 0;

    if(block >= S5_MAX_FILE_BLOCKS) return -ENOSPC;

    /* direct block case first */
    if(block < S5_NDIRECT_BLOCKS)
    {
        num = inode->s5_direct_blocks[block];
        if(num==0 && alloc == 1) /* create a new sparse data block */
        {
            int i_num = s5_alloc_block(s5);
            if(i_num < 0) return -ENOSPC; /* allocation didn't work */
            inode -> s5_direct_blocks[block] = i_num; /* set inode */
            s5_dirty_inode(s5,inode); /* b/c we set inode */
        }
        return num;
    }
    else
    {
        pframe_t* indirect_frame;
        int indirect_block = inode -> s5_indirect_block;
        if(indirect_block == 0) /* sparse indirect block */
        {
            if(alloc != 0) /* create a new sparse indirect block */
            {
                int i_indirect_block = s5_alloc_block(s5);
                if(i_indirect_block < 0) return -ENOSPC; /* allocation didn't work */
                inode -> s5_indirect_block = i_indirect_block; /* set inode */  
                s5_dirty_inode(s5,inode); /* b/c we set inode */
            }
            else /* just return 0 otherwise */
            {
                return indirect_block;
            }
        }

        /* getting indirect block */
        pframe_get(mm, indirect_block, &indirect_frame);
        pframe_pin(indirect_frame); /* b/c we're getting info from the indirect blocks */

        /* indirect block array */
        uint32_t* indirect_blocks = (uint32_t*)indirect_frame->pf_addr;
        num = *(indirect_blocks + block - S5_NDIRECT_BLOCKS);
        if(num == 0 && alloc != 0) { /* create a new sparse data block */

            int i_num = s5_alloc_block(s5);
            if(i_num < 0) {
                pframe_unpin(indirect_frame);
                return -ENOSPC;

            }
            *(indirect_blocks + block - S5_NDIRECT_BLOCKS) = i_num; /* set data block index in indirect block */
            pframe_dirty(indirect_frame); /* b/c we changed data block index */

        }
        pframe_unpin(indirect_frame);

        return num;
    }
}


/*
 * Locks the mutex for the whole file system
 */
static void
lock_s5(s5fs_t *fs)
{
        kmutex_lock(&fs->s5f_mutex);
}

/*
 * Unlocks the mutex for the whole file system
 */
static void
unlock_s5(s5fs_t *fs)
{
        kmutex_unlock(&fs->s5f_mutex);
}


/*
 * Write len bytes to the given inode, starting at seek bytes from the
 * beginning of the inode. On success, return the number of bytes
 * actually written (which should be 'len', unless there's only enough
 * room for a partial write); on failure, return -errno.
 *
 * This function should allow writing to files or directories, treating
 * them identically.
 *
 * Writing to a sparse block of the file should cause that block to be
 * allocated.  Writing past the end of the file should increase the size
 * of the file. Blocks between the end and where you start writing will
 * be sparse.
 *
 * Do not call s5_seek_to_block() directly from this function.  You will
 * use the vnode's pframe functions, which will eventually result in a
 * call to s5_seek_to_block().
 *
 * You will need pframe_dirty(), pframe_get(), memcpy().
 */
int
s5_write_file(vnode_t *vnode, off_t seek, const char *bytes, size_t len)
{
    s5_inode_t* inode = VNODE_TO_S5INODE(vnode);
    s5fs_t* s5 = VNODE_TO_S5FS(vnode);
    mmobj_t* mm = S5FS_TO_VMOBJ(s5);

    uint32_t block_index = S5_DATA_BLOCK(seek);
    uint32_t block_offset = S5_DATA_OFFSET(seek);
    if(block_index >= S5_MAX_FILE_BLOCKS) return -ENOSPC;

    uint32_t to_write = len;        
    uint32_t block_remaining = S5_BLOCK_SIZE - block_offset;
    int has_written = 0;

    while(to_write > 0 && block_index < S5_MAX_FILE_BLOCKS) {

        pframe_t * frame;
        pframe_get(&vnode->vn_mmobj, block_index, &frame);
        pframe_pin(frame);

        if(block_remaining >= to_write) /* last block */
        {
            memcpy((char*)frame->pf_addr + block_offset, (char*)bytes + has_written, to_write);
            has_written += to_write;
            to_write = 0;
            if(pframe_dirty(frame) <0)
            {
                pframe_unpin(frame);
                pframe_free(frame);
                return -ENOSPC;   
            }            
        }
        else
        {
            memcpy((char*)frame->pf_addr + block_offset, (char*)bytes + has_written, block_remaining);
            has_written += block_remaining;
            to_write -= block_remaining;
            block_offset = 0;
            block_remaining = S5_BLOCK_SIZE;
            block_index++;
            if(pframe_dirty(frame) <0)
            {
                pframe_unpin(frame);
                pframe_free(frame);
                return -ENOSPC;   
            }
        }

        pframe_unpin(frame);

    }
    if(seek + has_written > vnode-> vn_len) /* increment lengths if necessary */
    {
        vnode->vn_len = seek + has_written;
        inode->s5_size = seek + has_written;
        s5_dirty_inode(s5, inode); /* since we modified inode size */

    }

    return has_written;
}

/*
 * Read up to len bytes from the given inode, starting at seek bytes
 * from the beginning of the inode. On success, return the number of
 * bytes actually read, or 0 if the end of the file has been reached; on
 * failure, return -errno.
 *
 * This function should allow reading from files or directories,
 * treating them identically.
 *
 * Reading from a sparse block of the file should act like reading
 * zeros; it should not cause the sparse blocks to be allocated.
 *
 * Similarly as in s5_write_file(), do not call s5_seek_to_block()
 * directly from this function.
 *
 * If the region to be read would extend past the end of the file, less
 * data will be read than was requested.
 *
 * You probably want to use pframe_get(), memcpy().
 */
int
s5_read_file(struct vnode *vnode, off_t seek, char *dest, size_t len)
{
    s5_inode_t* inode = VNODE_TO_S5INODE(vnode);
    uint32_t block_index = S5_DATA_BLOCK(seek);
    uint32_t block_offset = S5_DATA_OFFSET(seek);
    if(block_index >= S5_MAX_FILE_BLOCKS) return 0;
    if(seek >= vnode->vn_len) return 0;

    uint32_t  to_read = len;
    if((uint32_t)seek + len > (uint32_t)vnode -> vn_len) to_read = vnode->vn_len - seek;

    uint32_t block_remaining = S5_BLOCK_SIZE - block_offset;
    int has_read = 0;

    while(to_read > 0 && block_index < S5_MAX_FILE_BLOCKS) {

        /* checking for sparse blocks first */

        int sparse_flag = 0;
        if(block_index < S5_NDIRECT_BLOCKS)
        {
            if(inode->s5_direct_blocks[block_index]==0) sparse_flag = 1;
        }
        else
        {
            /* indirect blocks */
            int indirect_block = inode -> s5_indirect_block;
            if(indirect_block == 0) sparse_flag = 1;
            else {

                s5fs_t* s5 = VNODE_TO_S5FS(vnode);
                mmobj_t* mm = S5FS_TO_VMOBJ(s5);

                pframe_t * indirect_frame;
                pframe_get(mm, indirect_block, &indirect_frame);
                uint32_t* indirect_blocks = (uint32_t*)indirect_frame->pf_addr;
                if(*(indirect_blocks + block_index - S5_NDIRECT_BLOCKS) == 0) sparse_flag = 1;
            }
        }

        pframe_t * frame;
        pframe_get(&vnode->vn_mmobj, block_index, &frame);

        pframe_pin(frame); /* b/c we're coping from the pframe */
        if(block_remaining >= to_read)
        {
            if(sparse_flag==1) memcpy((char*)dest + has_read, 0, to_read);
            else memcpy((char*)dest + has_read, (char*)frame->pf_addr + block_offset, to_read);
            has_read += to_read;
            to_read = 0;
        }
        else
        {
            if(sparse_flag==1) memcpy((char*)dest + has_read, 0, block_remaining);
            else memcpy((char*)dest + has_read, (char*)frame->pf_addr + block_offset, block_remaining);
            has_read += block_remaining;
            to_read -= block_remaining;
            block_offset = 0;
            block_remaining = S5_BLOCK_SIZE;
            block_index++;
        }
        pframe_unpin(frame); 
    }

    return has_read;
}

/*
 * Allocate a new disk-block off the block free list and return it. If
 * there are no free blocks, return -ENOSPC.
 *
 * This will not initialize the contents of an allocated block; these
 * contents are undefined.
 *
 * If the super block's s5s_nfree is 0, you need to refill 
 * s5s_free_blocks and reset s5s_nfree.  You need to read the contents 
 * of this page using the pframe system in order to obtain the next set of
 * free block numbers.
 *
 * Don't forget to dirty the appropriate blocks!
 *
 * You'll probably want to use lock_s5(), unlock_s5(), pframe_get(),
 * and s5_dirty_super()
 */
static int
s5_alloc_block(s5fs_t *fs)
{
    lock_s5(fs);
    mmobj_t* mm = S5FS_TO_VMOBJ(fs);
    s5_super_t *super = fs->s5f_super;

    int block_num;
    if(super->s5s_nfree != 0) { /* some free so we can just get the next one */
        block_num = super->s5s_free_blocks[super->s5s_nfree--];
    }
    else /* none free so either we're out or get more */
    {
        if((int)super->s5s_free_blocks[S5_NBLKS_PER_FNODE-1] == -1) /* out of free blocks */
        {
            unlock_s5(fs);
            return -ENOSPC;
        }

        /* now let's get more free blocks */
        int next_block = super->s5s_free_blocks[S5_NBLKS_PER_FNODE-1];
        pframe_t * frame;
        pframe_get(mm, next_block, &frame);
        pframe_pin(frame);
        memcpy((void*)super->s5s_free_blocks, frame->pf_addr, sizeof(uint32_t)*S5_NBLKS_PER_FNODE);
        block_num = next_block;
        super->s5s_nfree = S5_NBLKS_PER_FNODE - 1;
        pframe_unpin(frame);
    }

    s5_dirty_super(fs);
    unlock_s5(fs);
    return block_num;
}


/*
 * Given a filesystem and a block number, frees the given block in the
 * filesystem.
 *
 * This function may potentially block.
 *
 * The caller is responsible for ensuring that the block being placed on
 * the free list is actually free and is not resident.
 */
static void
s5_free_block(s5fs_t *fs, int blockno)
{
        s5_super_t *s = fs->s5f_super;


        lock_s5(fs);

        KASSERT(S5_NBLKS_PER_FNODE > s->s5s_nfree);

        if ((S5_NBLKS_PER_FNODE - 1) == s->s5s_nfree) {
                /* get the pframe where we will store the free block nums */
                pframe_t *prev_free_blocks = NULL;
                KASSERT(fs->s5f_bdev);
                pframe_get(&fs->s5f_bdev->bd_mmobj, blockno, &prev_free_blocks);
                KASSERT(prev_free_blocks->pf_addr);

                /* copy from the superblock to the new block on disk */
                memcpy(prev_free_blocks->pf_addr, (void *)(s->s5s_free_blocks),
                       S5_NBLKS_PER_FNODE * sizeof(int));
                pframe_dirty(prev_free_blocks);

                /* reset s->s5s_nfree and s->s5s_free_blocks */
                s->s5s_nfree = 0;
                s->s5s_free_blocks[S5_NBLKS_PER_FNODE - 1] = blockno;
        } else {
                s->s5s_free_blocks[s->s5s_nfree++] = blockno;
        }

        s5_dirty_super(fs);

        unlock_s5(fs);
}

/*
 * Creates a new inode from the free list and initializes its fields.
 * Uses S5_INODE_BLOCK to get the page from which to create the inode
 *
 * This function may block.
 */
int
s5_alloc_inode(fs_t *fs, uint16_t type, devid_t devid)
{
        s5fs_t *s5fs = FS_TO_S5FS(fs);
        pframe_t *inodep;
        s5_inode_t *inode;
        int ret = -1;

        KASSERT((S5_TYPE_DATA == type)
                || (S5_TYPE_DIR == type)
                || (S5_TYPE_CHR == type)
                || (S5_TYPE_BLK == type));


        lock_s5(s5fs);

        if (s5fs->s5f_super->s5s_free_inode == (uint32_t) -1) {
                unlock_s5(s5fs);
                return -ENOSPC;
        }

        pframe_get(&s5fs->s5f_bdev->bd_mmobj,
                   S5_INODE_BLOCK(s5fs->s5f_super->s5s_free_inode),
                   &inodep);
        KASSERT(inodep);

        inode = (s5_inode_t *)(inodep->pf_addr)
                + S5_INODE_OFFSET(s5fs->s5f_super->s5s_free_inode);

        KASSERT(inode->s5_number == s5fs->s5f_super->s5s_free_inode);

        ret = inode->s5_number;

        /* reset s5s_free_inode; remove the inode from the inode free list: */
        s5fs->s5f_super->s5s_free_inode = inode->s5_next_free;
        pframe_pin(inodep);
        s5_dirty_super(s5fs);
        pframe_unpin(inodep); 


        /* init the newly-allocated inode: */
        inode->s5_size = 0;
        inode->s5_type = type;
        inode->s5_linkcount = 0;
        memset(inode->s5_direct_blocks, 0, S5_NDIRECT_BLOCKS * sizeof(int));
        if ((S5_TYPE_CHR == type) || (S5_TYPE_BLK == type))
                inode->s5_indirect_block = devid;
        else
                inode->s5_indirect_block = 0;

        s5_dirty_inode(s5fs, inode);

        unlock_s5(s5fs);

        return ret;
}


/*
 * Free an inode by freeing its disk blocks and putting it back on the
 * inode free list.
 *
 * You should also reset the inode to an unused state (eg. zero-ing its
 * list of blocks and setting its type to S5_FREE_TYPE).
 *
 * Don't forget to free the indirect block if it exists.
 *
 * You probably want to use s5_free_block().
 */
void
s5_free_inode(vnode_t *vnode)
{
        uint32_t i;
        s5_inode_t *inode = VNODE_TO_S5INODE(vnode);
        s5fs_t *fs = VNODE_TO_S5FS(vnode);

        KASSERT((S5_TYPE_DATA == inode->s5_type)
                || (S5_TYPE_DIR == inode->s5_type)
                || (S5_TYPE_CHR == inode->s5_type)
                || (S5_TYPE_BLK == inode->s5_type));

        /* free any direct blocks */
        for (i = 0; i < S5_NDIRECT_BLOCKS; ++i) {
                if (inode->s5_direct_blocks[i]) {
                        dprintf("freeing block %d\n", inode->s5_direct_blocks[i]);
                        s5_free_block(fs, inode->s5_direct_blocks[i]);

                        s5_dirty_inode(fs, inode);
                        inode->s5_direct_blocks[i] = 0;
                }
        }

        if (((S5_TYPE_DATA == inode->s5_type)
             || (S5_TYPE_DIR == inode->s5_type))
            && inode->s5_indirect_block) {
                pframe_t *ibp;
                uint32_t *b;

                pframe_get(S5FS_TO_VMOBJ(fs),
                           (unsigned)inode->s5_indirect_block,
                           &ibp);
                KASSERT(ibp
                        && "because never fails for block_device "
                        "vm_objects");
                pframe_pin(ibp);

                b = (uint32_t *)(ibp->pf_addr);
                for (i = 0; i < S5_NIDIRECT_BLOCKS; ++i) {
                        KASSERT(b[i] != inode->s5_indirect_block);
                        if (b[i])
                                s5_free_block(fs, b[i]);
                }

                pframe_unpin(ibp);

                s5_free_block(fs, inode->s5_indirect_block);
        }

        inode->s5_indirect_block = 0;
        inode->s5_type = S5_TYPE_FREE;
        s5_dirty_inode(fs, inode);

        lock_s5(fs);
        inode->s5_next_free = fs->s5f_super->s5s_free_inode;
        fs->s5f_super->s5s_free_inode = inode->s5_number;
        unlock_s5(fs);

        s5_dirty_inode(fs, inode);
        s5_dirty_super(fs);
}

/*
 * Locate the directory entry in the given inode with the given name,
 * and return its inode number. If there is no entry with the given
 * name, return -ENOENT.
 *
 * You'll probably want to use s5_read_file and name_match
 *
 * You can either read one dirent at a time or optimize and read more.
 * Either is fine.
 */
int
s5_find_dirent(vnode_t *vnode, const char *name, size_t namelen)
{
    if(namelen == 0) return 0;
    /* st_dirent_t buffer */
    uint8_t buf[sizeof(s5_dirent_t)];
    s5_dirent_t * entry = (s5_dirent_t*)buf;
    int has_seen = 0;

    while(has_seen < vnode->vn_len)
    {
        int length = s5_read_file(vnode, has_seen,(char*)entry, sizeof(s5_dirent_t));
        if(length == sizeof(s5_dirent_t))
        {
            /*if(name_match(name, entry -> s5d_name, namelen)) { 
            JWAT: what in the actual f*** is with the input parameters?
                name was dev/null but namelen was 3
                -> used strncmp to shorten dev/null to 3 characters
                -> string tokenizer not working or some crap? adfkahdlfka
            */

            if(strncmp(name, entry->s5d_name, namelen) == 0) {
                return entry->s5d_inode;
            }
        }
        else break;
        has_seen += length;
    }

    return -ENOENT;
}

/*
 * Locate the directory entry in the given inode with the given name,
 * and delete it. If there is no entry with the given name, return
 * -ENOENT.
 *
 * In order to ensure that the directory entries are contiguous in the
 * directory file, you will need to move the last directory entry into
 * the remove dirent's place.
 *
 * When this function returns, the inode refcount on the removed file
 * should be decremented.
 *
 * It would be a nice extension to free blocks from the end of the
 * directory file which are no longer needed.
 *
 * Don't forget to dirty appropriate blocks!
 *
 * You probably want to use vget(), vput(), s5_read_file(),
 * s5_write_file(), and s5_dirty_inode().
 */
int
s5_remove_dirent(vnode_t *vnode, const char *name, size_t namelen)
{
    s5fs_t *s5 = VNODE_TO_S5FS(vnode);
    s5_inode_t* inode = VNODE_TO_S5INODE(vnode);
    uint8_t buf[sizeof(s5_dirent_t)];
    s5_dirent_t * entry = (s5_dirent_t*)buf;

    int del_inode_val = -1;
    int has_seen = 0;

    /* find directory entry first */
    while(has_seen < vnode->vn_len)
    {
        int length = s5_read_file(vnode, has_seen,(char*)entry, sizeof(s5_dirent_t));
        if(length == sizeof(s5_dirent_t))
        {
            if(strncmp(name, entry->s5d_name, namelen) == 0) {
                del_inode_val =  entry->s5d_inode;
                break;
            }
            has_seen += length;
        }
        else break; /* reached end */
    }
    if(del_inode_val < 0) return -ENOENT; /* no entry with given name */

    /* if entry to delete is not at the end...then read the last entry and write to delete loc */
    if(has_seen != (int)(vnode->vn_len - sizeof(s5_dirent_t))){
        s5_read_file(vnode, vnode->vn_len - sizeof(s5_dirent_t), (char*)entry, sizeof(s5_dirent_t));
        s5_write_file(vnode, has_seen, (char*)entry, sizeof(s5_dirent_t));
    }

    /* if we need to actually free a block... */
    if(S5_DATA_OFFSET(vnode->vn_len - sizeof(s5_dirent_t)) == 0){
        int index = S5_DATA_BLOCK(vnode->vn_len - sizeof(s5_dirent_t));
        
        if(index < S5_NDIRECT_BLOCKS) {
            s5_free_block(s5, inode->s5_direct_blocks[index]);
        }
        else { /* indirect blocks */
            int indirect_block = inode -> s5_indirect_block;
            KASSERT(indirect_block == 0);
            s5fs_t* s5 = VNODE_TO_S5FS(vnode);
            mmobj_t* mm = S5FS_TO_VMOBJ(s5);

            pframe_t * indirect_frame;
            pframe_get(mm, indirect_block, &indirect_frame);
            uint32_t* indirect_blocks = (uint32_t*)indirect_frame->pf_addr;

            s5_free_block(s5, *(indirect_blocks + index - S5_NDIRECT_BLOCKS));
        }
    }

    /* linkcounts! */
    vnode_t * del_vnode = vget(vnode->vn_fs, del_inode_val);
    s5_inode_t* del_inode = VNODE_TO_S5INODE(del_vnode);
    del_inode->s5_linkcount--;   
    s5_dirty_inode(s5, del_inode);
    vput(del_vnode);

    /* lengths! */
    s5_inode_t* dir_inode = VNODE_TO_S5INODE(vnode);
    vnode->vn_len -= sizeof(s5_dirent_t);
    dir_inode -> s5_size -= sizeof(s5_dirent_t);
    s5_dirty_inode(s5, dir_inode);

    return 0;
}

/*
 * Create a new directory entry in directory 'parent' with the given name, which
 * refers to the same file as 'child'.
 *
 * When this function returns, the inode refcount on the file that was linked to
 * should be incremented.
 *
 * Remember to incrament the ref counts appropriately
 *
 * You probably want to use s5_find_dirent(), s5_write_file(), and s5_dirty_inode().
 */
int
s5_link(vnode_t *parent, vnode_t *child, const char *name, size_t namelen)
{

    s5fs_t *s5 = VNODE_TO_S5FS(parent);
    s5_inode_t * child_inode = VNODE_TO_S5INODE(child);

    if(s5_find_dirent(parent, name, namelen) >= 0) return -1; /* already exists */

    /* new dirent */
    s5_dirent_t * new_dirent = kmalloc(sizeof(s5_dirent_t));
    memset(new_dirent, 0, sizeof(s5_dirent_t));
    new_dirent->s5d_inode = child_inode->s5_number;
    memcpy(new_dirent->s5d_name, name, namelen);

    new_dirent->s5d_name[namelen] = '\0'; /* add null-terminating character */

    /* write to file */
    s5_write_file(parent, parent->vn_len, (char*)new_dirent, sizeof(s5_dirent_t));
    kfree(new_dirent);

    /* increment ref count */
    child_inode->s5_linkcount++;

    /* dirty page */
    s5_dirty_inode(s5,child_inode);

    return 0;
}

/*
 * Return the number of blocks that this inode has allocated on disk.
 * This should include the indirect block, but not include sparse
 * blocks.
 *
 * This is only used by s5fs_stat().
 *
 * You'll probably want to use pframe_get().
 */
int
s5_inode_blocks(vnode_t *vnode)
{

    /* up to S5_NDIRECT_BLOCKS + indirect block + indirect data blocks up to S5_DATA_BLOCK(vnode->vn_len) */

    s5_inode_t* inode = VNODE_TO_S5INODE(vnode);
    int max_block = S5_DATA_BLOCK(vnode->vn_len);

    int num_blocks = 0;
    int cur_block = 0;

    /* dirent blocks */
    while(cur_block <= max_block && cur_block < S5_NDIRECT_BLOCKS) {

        if(inode->s5_direct_blocks[cur_block]!=0) num_blocks++;
        cur_block++;
    }

    /* one indirect block */
    int indirect_block = inode -> s5_indirect_block;
    if(cur_block <= max_block && indirect_block == 0) return num_blocks;
    else if(cur_block > max_block) return num_blocks;
    else num_blocks++;

    /* indirect data blocks */

    s5fs_t* s5 = VNODE_TO_S5FS(vnode);
    mmobj_t* mm = S5FS_TO_VMOBJ(s5);

    pframe_t * indirect_frame;
    pframe_get(mm, indirect_block, &indirect_frame);
    pframe_pin(indirect_frame); /* b/c we're using it for a while */

    uint32_t* indirect_blocks = (uint32_t*)indirect_frame->pf_addr;

    while(cur_block <= max_block) {

        if(*(indirect_blocks + cur_block - S5_NDIRECT_BLOCKS) != 0) num_blocks++;
        cur_block++;                
    }

    pframe_unpin(indirect_frame);
    
    return num_blocks;
}
